﻿#region File and License Information
/*
<File>
	<License>
		Copyright © 2009 - 2017, Daniel Vaughan. All rights reserved.
		This file is part of Calcium (http://calciumsdk.net), 
		which is released under the MIT License.
		See file /Documentation/License.txt for details.
	</License>
	<CreationDate>2010-08-18 17:44:10Z</CreationDate>
</File>
*/
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.ServiceModel.Channels;
using System.Threading;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
using System.Windows.Media.Imaging;

using Coding4Fun.Toolkit.Controls;

using Microsoft.Phone.Controls;

using Outcoder.Services.DialogService;

using InputPrompt = Outcoder.UI.Xaml.Controls.InputPrompt;
using PopUpResult = Coding4Fun.Toolkit.Controls.PopUpResult;
using OutcoderPopUpResult = Outcoder.UI.Xaml.Controls.PopUpResult;

namespace Outcoder.Services.Implementation
{
	public partial class DialogService : DialogServiceBase
	{
		//MessagePrompt messagePrompt;
		readonly int millisecondsToAllowAppBarToClose = 150;
		
		MessagePrompt CreateOrRetrieveMessagePrompt()
		{
			return new MessagePrompt(); //messagePrompt ?? (messagePrompt = new MessagePrompt());
		}

		public bool WaitForSystemTrayToClose { get; set; }

		public override async Task<int?> ShowDialogAsync(
			object body, IEnumerable<object> buttons, string caption = null,
			int defaultAcceptButtonIndex = -1,
			object details = null,
			DialogController dialogController = null)
		{
			await GiveTimeForAppBarToClose();

			TaskCompletionSource<int?> resultSource = new TaskCompletionSource<int?>();

			MessagePrompt messagePrompt = CreateOrRetrieveMessagePrompt();
			messagePrompt.Title = caption;
			string bodyText = body as string;
			if (bodyText != null)
			{
				var textBlock = new TextBlock
									{
										Text = bodyText,
										TextWrapping = TextWrapping.Wrap
									};
				messagePrompt.Body = textBlock;
			}
			else
			{
				messagePrompt.Body = body;
			}
			
			messagePrompt.ActionPopUpButtons.Clear();
			int? tappedButtonIndex = null;

			if (buttons != null)
			{
				int count = -1;

				foreach (var button in buttons)
				{
					count++;

					Button buttonToAdd = button as Button;
					if (buttonToAdd == null)
					{
						buttonToAdd = new Button { Content = button };
					}

					int buttonIndex = count;
					buttonToAdd.Tap += delegate
					{
						tappedButtonIndex = buttonIndex;
						messagePrompt.Hide();
					};

					messagePrompt.ActionPopUpButtons.Add(buttonToAdd);
				}
			}

			EventHandler closeRequestedHandler = null;

			if (dialogController != null)
			{
				closeRequestedHandler = (sender, args) =>
					{
						dialogController.CloseRequested -= closeRequestedHandler;
						messagePrompt.Hide();
					};

				dialogController.CloseRequested += closeRequestedHandler;
			}

			EventHandler<PopUpEventArgs<string, PopUpResult>> completedHandler = null;

			completedHandler = (sender, args) =>
				{
					if (dialogController != null && closeRequestedHandler != null)
					{
						dialogController.CloseRequested -= closeRequestedHandler;
					}

					messagePrompt.Completed -= completedHandler;
					resultSource.SetResult(tappedButtonIndex);
				};
			messagePrompt.Completed += completedHandler;

			try
			{
				Interlocked.Increment(ref openDialogCount);
				messagePrompt.Show();

				var result = await resultSource.Task;
				return result;
			}
			catch
			{
				messagePrompt.Completed -= completedHandler;

				throw;
			}
			finally
			{
				Interlocked.Decrement(ref openDialogCount);
			}			
		}

		/// <summary>
		/// If you prefer the Okay button on the right when calling ShowDialogAsync,
		/// set this property to <c>true</c>.
		/// </summary>
		public bool PlaceOkayButtonToTheRightOfCancel { get; set; }

		public override async Task<DialogResult> ShowDialogAsync(
			object content, 
			string caption, 
			DialogButton dialogButton,
			DialogImage dialogImage = DialogImage.None, 
			DialogImportance importanceThreshold = DialogImportance.High, 
			object details = null,
			DialogController dialogController = null)
		{
			await GiveTimeForAppBarToClose();

			MessagePrompt messagePrompt = CreateOrRetrieveMessagePrompt();

			if (PlaceOkayButtonToTheRightOfCancel)
			{
				/* Hack to place okay button on the right. */
				var buttons = messagePrompt.ActionPopUpButtons;
				if (buttons.Count > 1)
				{
					var okButton = buttons.First();
					buttons.Remove(okButton);
					buttons.Add(okButton);
				}
			}

			messagePrompt.Title = caption;

			string bodyText = content as string;
			if (bodyText != null)
			{
				var textBlock = new TextBlock
				{
					Text = bodyText,
					TextWrapping = TextWrapping.Wrap
				};
				messagePrompt.Body = textBlock;
			}
			else
			{
				messagePrompt.Body = content;
			}

			switch (dialogButton)
			{
				case DialogButton.OK:
					messagePrompt.IsCancelVisible = false;
					break;
				case DialogButton.OKCancel:
					messagePrompt.IsCancelVisible = true;
					break;
				case DialogButton.YesNo:
					/* The cancel button, represented by a cross, is used as the 'no' button. */
					messagePrompt.IsCancelVisible = true;
					break;
				case DialogButton.YesNoCancel:
					/* This is not properly implemented. 
					 * Should show three buttons, but requires localization. */
					messagePrompt.IsCancelVisible = true;
					break;
				default:
					throw new Exception("Unknown DialogButton: " + dialogButton);
			}
			
			EventHandler closeRequestedHandler = null;

			if (dialogController != null)
			{
				closeRequestedHandler = (sender, args) =>
				{
					dialogController.CloseRequested -= closeRequestedHandler;
					messagePrompt.Hide();
				};

				dialogController.CloseRequested += closeRequestedHandler;
			}

			var resultSource = new TaskCompletionSource<DialogResult>();
			bool completedRaised = false;

			EventHandler<PopUpEventArgs<string, PopUpResult>> completedHandler = null;
			completedHandler = delegate(object sender, PopUpEventArgs<string, PopUpResult> args)
				{
					try
					{
						if (dialogController != null && closeRequestedHandler != null)
						{
							dialogController.CloseRequested -= closeRequestedHandler;
						}

						messagePrompt.Completed -= completedHandler;

						if (completedRaised)
						{
							return;
						}
						completedRaised = true;
					
						DialogResult tapResult;
						PopUpResult popUpResult = args.PopUpResult;

						if (popUpResult == PopUpResult.Cancelled)
						{
							/* If it's a Yes/No dialog then a cancel indicates that the 'no' (cross) was tapped. */
							if (dialogButton == DialogButton.YesNo)
							{
								tapResult = DialogResult.No;
							}
							else
							{
								tapResult = DialogResult.Cancel;
							}
						}
						else if (popUpResult == PopUpResult.NoResponse)
						{
							tapResult = DialogResult.None;
						}
						else if (popUpResult == PopUpResult.UserDismissed)
						{
							tapResult = DialogResult.None;
						}
						else if (popUpResult == PopUpResult.Ok)
						{
							if (dialogButton == DialogButton.YesNo
							    || dialogButton == DialogButton.YesNoCancel)
							{
								tapResult = DialogResult.Yes;
							}
							else
							{
								tapResult = DialogResult.OK;
							}
						}
						else
						{
							throw new Exception("DialogResult was unable to be determined.");
						}

						resultSource.SetResult(tapResult);
					}
					catch (Exception ex)
					{
						resultSource.SetException(ex);
					}
				};

			

			try
			{
				messagePrompt.Completed += completedHandler;

				Interlocked.Increment(ref openDialogCount);

				messagePrompt.Show();
			}
			catch (Exception ex)
			{
				Interlocked.Decrement(ref openDialogCount);
				messagePrompt.Completed -= completedHandler;
				throw;
			}

			DialogResult result;
			try
			{
				result = await resultSource.Task;
			}
			finally
			{
				Interlocked.Decrement(ref openDialogCount);
			}

			return result;
		}

		/// <summary>
		/// This method wait for a moment to allow the AppBar to close. 
		/// The reason is that toasts and dialogs get there why offset according to whether the system tray is open. 
		/// In some apps, the ApplicationBar's menu expansion. I.e., if the app bar is open then the system tray is shown. 
		/// When the app bar is closed the system tray is hidden. 
		/// When a story board is used for the transition we need to allow it to start.
		/// </summary>
		/// <returns></returns>
		async Task GiveTimeForAppBarToClose()
		{
			if (WaitForSystemTrayToClose)
			{
				/* Wait for AppBar to close because if the system tray's visibility then the dialog will be off. */
				await Task.Delay(TimeSpan.FromMilliseconds(millisecondsToAllowAppBarToClose));
				//Thread.Sleep(millisecondsToAllowAppBarToClose);
			}
		}

		public override async Task<QuestionResponse<TResponse>> AskQuestionAsync<TResponse>(
			IQuestion<TResponse> question)
		{
			ArgumentValidator.AssertNotNull(question, "body");

			await GiveTimeForAppBarToClose();

			var textResponseQuestion = question as TextResponseQuestion;
			if (textResponseQuestion != null)
			{
				var textBlock = new TextBlock
				{
					Text = textResponseQuestion.Question,
					TextWrapping = TextWrapping.Wrap
				};

				InputScope inputScope = new InputScope();
				inputScope.Names.Add(new InputScopeName { NameValue = textResponseQuestion.InputScope });

				var textBox = new TextBox
					{
						InputScope = inputScope, 
						Text = textResponseQuestion.DefaultResponse ?? string.Empty,
						Margin = new Thickness(-12,12,-12,12)
					};
				//textBox.Loaded += HandleTextBoxLoaded;
				//textBox.KeyUp += (sender, args) => { };
				
				StackPanel panel = new StackPanel();
				panel.Children.Add(textBlock);
				panel.Children.Add(textBox);

				var showDialogResult = await ShowDialogAsync(panel, textResponseQuestion.Caption, DialogButton.OKCancel);
				var okCancelQuestionResult = showDialogResult == DialogResult.OK || showDialogResult == DialogResult.Yes 
											? OkCancelQuestionResult.Ok : OkCancelQuestionResult.Cancel;
				TextResponse textResponse = new TextResponse(okCancelQuestionResult, textBox.Text);
				//var response = new QuestionResponse<TextResponse>(textResponse, textResponseQuestion);
				var result = new QuestionResponse<TResponse>((TResponse)(object)textResponse, question);
				return result;
			}

			var twoButtonQuestion = question as TwoButtonQuestion;
			if (twoButtonQuestion == null)
			{
				throw new NotImplementedException("Currently this async method only supports TwoButtonQuestion questions.");
			}

			string caption = twoButtonQuestion.Caption != null ? twoButtonQuestion.Caption.ToString() : null;
			string message = twoButtonQuestion.Body != null ? twoButtonQuestion.Body.ToString() : null;

			CustomMessageBox messageBox = new CustomMessageBox()
				{
					LeftButtonContent = twoButtonQuestion.LeftButtonContent,
					RightButtonContent = twoButtonQuestion.RightButtonContent,
					Caption = caption,
					Message = message
				};

			var source = new TaskCompletionSource<QuestionResponse<TResponse>>();

			messageBox.Dismissed += (sender, args) =>
				{
					TwoButtonResponseValue responseValue;
					
					switch (args.Result)
					{
						case CustomMessageBoxResult.LeftButton:
							responseValue = TwoButtonResponseValue.LeftButton;
							break;
						case CustomMessageBoxResult.RightButton:
							responseValue = TwoButtonResponseValue.RightButton;
							break;
						default:
							responseValue = TwoButtonResponseValue.None;
							break;
					}

					//TwoButtonResponse response = new TwoButtonResponse(responseValue);
					var result = new QuestionResponse<TResponse>((TResponse)(object)responseValue, question);
					try
					{
						source.SetResult(result);
					}
					catch (InvalidOperationException)
					{
						/* When deactivation occurrs, an exception 
						 * may be raised inexplicably on reactivation - ignore. */
					}
				};

			try
			{
				Interlocked.Increment(ref openDialogCount);

				messageBox.Show();
			}
			catch (Exception ex)
			{
				Interlocked.Decrement(ref openDialogCount);
				throw;
			}

			try
			{
				return await source.Task;
			}
			finally
			{
				Interlocked.Decrement(ref openDialogCount);
			}
			
		}

		void HandleTextBoxLoaded(object sender, RoutedEventArgs e)
		{
			var textBox = (Control)sender;
			textBox.Focus();
			textBox.Loaded -= HandleTextBoxLoaded;
		}

		void AskQuestionWithTextResponse(
			TextResponseQuestion textResponseQuestion,
			Action<QuestionResponse<TextResponse>> resultHandler)
		{
			try
			{
				AskQuestionWithTextResponseCore(textResponseQuestion, resultHandler);
			}
			catch (Exception ex)
			{
				Console.WriteLine(ex);
				Deployment.Current.Dispatcher.BeginInvoke(
					delegate
						{
							resultHandler(new QuestionResponse<TextResponse>(null, textResponseQuestion, ex));
						});
			}
		}

		void AskQuestionWithTextResponseCore(
			TextResponseQuestion textResponseQuestion,
			Action<QuestionResponse<TextResponse>> resultHandler)
		{
			OkCancelQuestionResult questionResult;

			Deployment.Current.Dispatcher.InvokeIfRequired(
				delegate
			        {
			        	try
			        	{
							InputScope inputScope = new InputScope();
							inputScope.Names.Add(new InputScopeName { NameValue = textResponseQuestion.InputScope });

							InputPrompt prompt = new InputPrompt
							                     	{
							                     		Title = textResponseQuestion.Caption, 
														Message = textResponseQuestion.Question,
														Value = textResponseQuestion.DefaultResponse,
														InputScope = inputScope
							                     	};

			        		bool[] callThrewException = {false};

							prompt.Completed += (sender, args) =>
							                    	{
							                    		if (!callThrewException[0])
							                    		{
							                    			Interlocked.Decrement(ref openDialogCount);
							                    		}

														if (args.Error != null)
														{
															resultHandler(new QuestionResponse<TextResponse>(null, textResponseQuestion, args.Error));
															return;
														}

														questionResult = args.PopUpResult == OutcoderPopUpResult.OK 
															? OkCancelQuestionResult.Ok 
															: OkCancelQuestionResult.Cancel;

														var textResponse = new TextResponse(questionResult, args.Result);
														var result = new QuestionResponse<TextResponse>(textResponse, textResponseQuestion);
														resultHandler(result);
							                    	};
			        		try
			        		{
								Interlocked.Increment(ref openDialogCount);
								prompt.Show();
			        		}
			        		catch (Exception)
			        		{
			        			callThrewException[0] = true;
			        			Interlocked.Decrement(ref openDialogCount);
			        		}
			        	}
			        	catch (Exception ex)
			        	{
			        		Console.WriteLine(ex);
							resultHandler(new QuestionResponse<TextResponse>(null, textResponseQuestion, ex));
			        	}
			        });

		}

		public override async Task<object> ShowToastAsync(ToastParameters toastParameters)
		{
			ArgumentValidator.AssertNotNull(toastParameters, "toastParameters");

			await GiveTimeForAppBarToClose();

			ToastPrompt toast = new ToastPrompt();

			var body = toastParameters.Body;
			if (body != null)
			{
				/* Hopefully we can open this up to an object later. */
				string bodyText = body.ToString();
				if (!string.IsNullOrWhiteSpace(bodyText))
				{
					toast.Message = bodyText;
					toast.TextWrapping = TextWrapping.Wrap;
				}
			}

			var caption = toastParameters.Caption;
			if (caption != null)
			{
				/* Hopefully we can open this up to an object later. */
				var captionText = caption.ToString();
				if (!string.IsNullOrWhiteSpace(captionText))
				{
					toast.Title = captionText;
				}
			}

			ToastTextOrientation? textOrientation = toastParameters.TextOrientation;
			if (textOrientation.HasValue)
			{
				ToastTextOrientation orientation = textOrientation.Value;
				toast.TextOrientation = orientation == ToastTextOrientation.Horizontal
											? Orientation.Horizontal
											: Orientation.Vertical;
			}
			//else if (string.IsNullOrWhiteSpace(toastParameters.Caption) ||
			//	string.IsNullOrWhiteSpace(toastParameters.Message))
			//{
			//	toast.TextOrientation = Orientation.Horizontal;
			//}

			if (toastParameters.MillisecondsUntilHidden.HasValue)
			{
				toast.MillisecondsUntilHidden = toastParameters.MillisecondsUntilHidden.Value;
			}

			Uri imageUri = toastParameters.ImageUri;
			if (imageUri != null)
			{
				toast.ImageSource = new BitmapImage(imageUri);
			}

			double? imageWidth = toastParameters.ImageWidth;
			if (imageWidth.HasValue)
			{
				toast.ImageWidth = imageWidth.Value;
			}

			double? imageHeight = toastParameters.ImageHeight;
			if (imageHeight.HasValue)
			{
				toast.ImageWidth = imageHeight.Value;
			}

			if (toastParameters.VerticalOrientation.HasValue)
			{
				switch (toastParameters.VerticalOrientation.Value)
				{
					case ToastVerticalOrientation.Top:
						toast.VerticalAlignment = VerticalAlignment.Top;
						break;
					case ToastVerticalOrientation.Center:
						toast.VerticalAlignment = VerticalAlignment.Center;
						break;
					case ToastVerticalOrientation.Bottom:
						toast.VerticalAlignment = VerticalAlignment.Bottom;
						var appBar = Page.ApplicationBar;
						if (appBar.IsVisible && appBar.Opacity < 1)
						{
							toast.Margin = new Thickness(0,0,0,72);
						}
						break;
					case ToastVerticalOrientation.Stretch:
						toast.VerticalAlignment = VerticalAlignment.Stretch;
						break;
				}
			}
			
			TaskCompletionSource<object> source = new TaskCompletionSource<object>();
			EventHandler<PopUpEventArgs<string, PopUpResult>> completedHandler = null;
			
			completedHandler = (o, e) =>
				{
					toast.Completed -= completedHandler;

					Exception error = e.Error;
					if (error != null)
					{
						source.SetException(error);
					}
					else
					{
						source.SetResult(null);
					}
				};
			
			toast.Completed += completedHandler;
			toast.Show();

			return await source.Task;
		}

		internal PhoneApplicationPage Page
		{
			get 
			{ 
				return RootFrame.GetFirstLogicalChildByType<PhoneApplicationPage>(false);
			}
		}

		static Frame RootFrame
		{
			get
			{
#if WINDOWS_STORE
				var rootFrame = Window.Current.Content as Frame;
#elif WINDOWS_PHONE
				var rootFrame = Application.Current.RootVisual as Frame;
#endif

				return rootFrame;
			}

		}
	}

//	public class TextResponseViewModel
//	{
//		public TextResponseQuestion TextResponseQuestion { get; private set; }
//		public string Response { get; set; }
//		public string Caption { get; private set; }
//		public string Question { get; private set; }
//
//		public TextResponseViewModel(TextResponseQuestion textResponseQuestion)
//		{
//			TextResponseQuestion = textResponseQuestion;
//			Caption = string.IsNullOrEmpty(textResponseQuestion.Caption) 
//				? "Question" : textResponseQuestion.Caption;
//			Question = textResponseQuestion.Question;
//		}
//	}
}
